
#include <dynload.h>
#include <dyncall.h>

#include <string.h>

#include "../main.h"


typedef struct lib_t {
	char *name;
	void *handle;
} lib_t;




//////////////////////////////////////////////////////////////////////////////
//
static JSBool construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
	lib_t *lib = (lib_t *)calloc(sizeof(lib_t), 1);
	JS_ConvertArguments(cx, 1, argv, "s", &lib->name);
	JS_SetPrivate(cx, obj, lib);
	return JS_TRUE;
}


enum types { NI_BOOL=1, NI_CHAR, NI_SHORT, NI_INT, NI_LONG, NI_LONGLONG, NI_FLOAT, NI_DOUBLE, NI_POINTER, NI_VOID, NI_CSTRING };


typedef struct T {
	char *name;
	int nargs;
	int *spec;
	int return_type;
	void *handle;
	lib_t *lib;
} T;



//////////////////////////////////////////////////////////////////////////////
//
static JSBool native_wrapper (JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
	trace1("native_wrapper");
	
	trace2("getting function private");

	JSObject *callee = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv));
	
	JSObject *parent = JS_GetParent(cx, callee);

	T *x = (T *)JS_GetPrivate(cx, parent);
	
	
	trace2("function name: %s", x->name);
		
	lib_t *lib = x->lib;
	
	trace2("lib name: %s", lib->name);
	

	if(!x->lib->handle) {
		trace2("opening library %s.", x->lib->name);
		if(!(x->lib->handle = dlLoadLibrary(x->lib->name))) {
			JS_ReportError(cx, "dlLoadLibrary: cannot load library %s.", x->lib->name);
			return JS_FALSE;
		}
	}
	
	if(!x->handle) {
		trace2("finding symbol '%s' in %s.", x->name, x->lib->name);
		if(!(x->handle = dlFindSymbol((DLLib *)x->lib->handle, x->name))) {
			JS_ReportError(cx, "dlFindSymbol: cannot find symbol %s in library %s.", x->name, x->lib->name);
			return JS_FALSE;
		}
	}

	
	trace2("initializing dccall");
	DCCallVM *vm = dcNewCallVM(4096);
	dcMode(vm, DC_CALL_C_DEFAULT);
		
	int i;
	jsval v;
	char c;
	short s;
	//int d;
	long l;
	long long ll;
	float f;
	double d;
	void *p;
	
	
	//void *funcPtr = dlFindSymbol(x->lib->handle, x->name);
	


	
	trace2("setting call arguments");
	int n;
	for (n = 0; n < x->nargs; ++n) {
		switch(x->spec[n]) {
			case NI_BOOL:     JS_ConvertArguments(cx, 1, argv+n, "c", &c); dcArgBool    (vm, c); break;
			case NI_CHAR:     JS_ConvertArguments(cx, 1, argv+n, "c", &c); dcArgChar    (vm, c); break;
			case NI_SHORT:    JS_ConvertArguments(cx, 1, argv+n, "c", &c); dcArgShort   (vm, c); break;
			case NI_INT:      JS_ConvertArguments(cx, 1, argv+n, "i", &i); dcArgInt     (vm, i); break;
			case NI_LONG:     JS_ConvertArguments(cx, 1, argv+n, "j", &l); dcArgLong    (vm, l); break;
			case NI_LONGLONG: JS_ConvertArguments(cx, 1, argv+n, "j", &l); dcArgLongLong(vm, l); break;
			case NI_FLOAT:    JS_ConvertArguments(cx, 1, argv+n, "d", &d); dcArgFloat   (vm, d); break;
			case NI_DOUBLE:   JS_ConvertArguments(cx, 1, argv+n, "d", &d); dcArgDouble  (vm, d); break;
			case NI_POINTER:  fatal("pointer conversion not supported yet"); break;

			case NI_CSTRING: 
				trace2("pushing cstring argument");
				char *cstr;
				JS_ConvertArguments(cx, 1, argv+n, "s", &cstr);
				trace2("cstring argument value: %s.", cstr);
				dcArgPointer(vm, cstr);
				break;
		}
	}
			
	DCpointer func_ptr = (DCpointer)x->handle;
	
	trace2("invoking...");
	switch(x->return_type) {
		case NI_BOOL:     c = dcCallBool    (vm, func_ptr); *rval = INT_TO_JSVAL(c); break;
		case NI_CHAR:     c = dcCallChar    (vm, func_ptr); *rval = INT_TO_JSVAL(c); break;
		case NI_SHORT:    c = dcCallShort   (vm, func_ptr); *rval = INT_TO_JSVAL(c); break;
		case NI_INT:      i = dcCallInt     (vm, func_ptr); *rval = INT_TO_JSVAL(i); break;
		case NI_LONG:     l = dcCallLong    (vm, func_ptr); *rval = INT_TO_JSVAL(l); break;
		case NI_LONGLONG: l = dcCallLongLong(vm, func_ptr); *rval = INT_TO_JSVAL(l); break;
		case NI_FLOAT: case NI_DOUBLE:
			d = dcCallFloat(vm, func_ptr);
			JS_NewNumberValue(cx, d, rval);
			break;
		case NI_POINTER:  fatal("pointer conversion not supported yet"); break;

		case NI_VOID: dcCallVoid(vm, func_ptr); *rval = JSVAL_VOID; break;
	}
	trace2("DONE!");
	
	
	trace2("returning");
	*rval = JSVAL_VOID;
	return JS_TRUE;
	
	//dcCall
	
	
}




//////////////////////////////////////////////////////////////////////////////
//
static JSBool getFunctionWrapper (JSContext *cx, uintN argc, jsval *vp) {
	trace1("getFunctionWrapper");
	
	T *x = (T *)calloc(sizeof(T), 1);
	
	//char *func_name;

	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	
	jsval *argv = JS_ARGV(cx, vp);
	
	if(argc < 2)
		return JS_FALSE;
	
	if (!JS_ConvertArguments(cx, argc, argv++, "s", &x->name))
		return JS_FALSE;

	trace2("preparing func");

	
	trace2("func prepared");
	
	x->nargs = argc - 2;
	x->spec = (int *)malloc(sizeof(int) * 10);
	
	trace2("setting return type");
	x->return_type = JSVAL_TO_INT(*(argv++));
	
	trace2("setting arguments");
	int i;
	for (i = 0; i < x->nargs; ++i)
		x->spec[i] = JSVAL_TO_INT(*(argv++));

	//trace2("function name: %s", x->name);

	x->lib = (lib_t *)JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));


	JSObject *parent = JS_NewObject(cx, NULL, NULL, NULL);
	trace2("setting parent private");
	JS_SetPrivate(cx, parent, x);
	
	trace2("creating new function with given parent");
	JSFunction *func = JS_NewFunction(cx, native_wrapper, x->nargs, 0, parent, NULL);

	trace2("getting function object");
	JSObject *func_obj = JS_GetFunctionObject(func);

	trace2("converting function object to rval");
	JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(func_obj));
	
	trace2("returning");
	return JS_TRUE;
}



static JSClass __class = {
    "Native", 0,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};


static JSPropertySpec props[] = {
    {0}
};

static JSFunctionSpec methods[] = {
	JS_FN("getFunctionWrapper",  getFunctionWrapper, 0, 0),
	JS_FS_END
};



//////////////////////////////////////////////////////////////////////////////
//
void class_native_init(JSContext *cx){
	JSObject *global = JS_GetGlobalObject(cx);
	JSObject *Server = JS_InitClass(cx, global, global, &__class, construct, 2, NULL, methods, NULL, NULL);
}



